# -*- coding: utf-8 -*-
#Importamos las librerias necesarias para realizar los calculos...
import random as r#Libreria para manejo de numeros aleatorios
import datetime as d#Libreria para manejo de tiempos //utilizaremos para performance...
import config as c#Archivo de funciones de apoyo y parametros
import busquedas as b#Archivo con algoritmos de busqueda
import estructuras as e#Libreria de creacion de las estructuras basicas de trabajo

##
# Definimos la clase problema aplicable a este ejercicio
class problema(b.problema):#Redefinimos la clase
    def __init__(self, datos, initial=None, prioridad_prestamo=(c.comparar[2],False)):#Constructor de la clase sonsin [Nuestro agente con carencia de IQ], si no pasamos opcionales, todo random...
        self.total= datos[0] #Conjunto total de prestamos en control de la financiera
        self.remplazar= datos[1] #Cartera a remplazar devuelta por entidad externa
        self.valor_objetivo= self.remplazar.valor_total() #Para no recalcular, y evitar uso de cpu inutil...
        self.clientes= set([p.id_cliente for p in self.remplazar.prestamos.values()]) #Conjunto que contiene clientes de cartera devuelta
        self.total.calcular_preferencias(self.clientes) #Una vez tenemos la lista de clientes devueltos, calculamos preferencias :P
        self.lista_extendida= c.lista_ponderada(sorted(self.total.prestamos.values(),cmp=prioridad_prestamo[0], reverse=prioridad_prestamo[1]))#Creamos lista probabilistica de ids de prestamos segun nuestra eleccion...
        self.initial= initial  or self.estado_random()# Puede recibir uno estado inicial o generar uno totalmetne aleatorio...
        
    def successor(self, state): #Funcion encargada de generar los proximos sucesores
        if state.valor_total() < self.valor_objetivo: return [('Agregamos', state.mod_cartera(r.choice(self.lista_extendida))) for x in range(c.indice_ramificacion)]
        else: return[('Quitamos', state.mod_cartera(r.choice(state.prestamos.values()))) for x in range(c.indice_ramificacion)]
        #Estas serian las lineas de codigo para tener un sucesores canonico, respetando definicion de colina. (Todas las opciones posibles)
        #if state.valor_total() < self.valor_objetivo: return [('Agregamos', state.mod_cartera(p)) for p in self.total.prestamos.values()]
        #else: return [('Quitamos', state.mod_cartera(p)) for p in state.prestamos.values()]

    def goal_test(self, state): #Funcion encargada de definir en la expancion si el estado actual es objetivo, devuelve un booleano referente al exito.
        return ((state.valor_total() >= self.valor_objetivo) and (state.valor_total()<= (self.valor_objetivo * 1.01))) #El valor de la cartera es objetivo

    def estado_random(self): #Funcion encargada de generar un estado valido totalmente al azar...
        valor_objetivo= self.valor_objetivo * c.porcentaje() #Crear aleatoriamente el valor total a alcanzar en el nuevo estado
        lista_prestamos= c.lista_ponderada(c.random_sort_list(self.total.prestamos.values()))#Crea la lista probabilistica extendida
        state= e.cartera()#Instancia la nueva cartera que devolvera
        while state.valor_total() < valor_objetivo: state.agregar_prestamo(r.choice(lista_prestamos)) #Selecciona prestamos al azar hasta alcanzar total deseado
        print("Se genero aleatoriamente una cartera inicial con %s prestamos que suma un valor total de %s."%(len(state.prestamos),state.valor_total()))
        return state#Devuelve la cartera nueva totalmente al azar...

    def path_cost(self,state): #Funcion encargada de definir el costo del camino
        return 1 #Como costo usaremos la plata que llevamos agregada, no es excelente, pero cost + value puede andar!

    def value(self, state): #Devuelve un valor de calidad para permitirle comprar entre dos estados...
        if len(state.prestamos)==0: return 0 #Si la cartera esta vacia, directamente no la analiza.
        elif not self.goal_test(state): return 0#Si la cartera no es objetivo, directametne no la analiza.
        cartera= state.valor_total() #Evitamos uso de cpu innecesario a cambio de memoria...
        usuarios= (self.valor_objetivo * 0.05) * (len([p for p in state.prestamos.values() if p.id_cliente in self.clientes]) / len(self.clientes))## (VO x 15%) x (usuarios repetidos / usuarios en cartera remplazar)
        mora= c.prom([p.mora[0] for p in state.prestamos.values()]) * (cartera * 0.05)#Darle preferencia a los prestamos menos morosos //promedio de moras actuales x 5% total objetivo
        refinanciamiento= c.prom([p.refinanciamientos for p in state.prestamos.values()]) * (cartera * 0.05)#Daremos preferencia a la menor cantidad de refinanciaciones//promedio de refinanciamientos x 5% total objetivo
        if cartera <= self.valor_objetivo: res= cartera #Mientras este en rango, crecera en busca de la mas optima...
        elif cartera <= self.valor_objetivo * 1.01: res= cartera - ((cartera - self.valor_objetivo) ** 2) #Es meta, pero no optima, por ende penalizamos en lo que se paso
        else: res= cartera - (cartera - self.valor_objetivo)* 5 #Si nos pasamos, se penaliza en mayor medida que si estariamos por debajo, pero podemos ir sacando prestamos...
        return int(res + usuarios - mora - refinanciamiento) #Entregamos la formula final...

##
# Funciones para simplificar el trabajo
def carga_archivos():
    T_inicio=d.datetime.now()
    print("Iniciamos procesamiento de archivos.\nCalculando...")
    prestamos= c.csv_to_dict(c.archivos_financiera[0])#Generamos el diccionario a partir del archivo de todos nuestros prestamos
    cuotas= c.csv_to_dict(c.archivos_financiera[1])#Generamos el diccionario a partir del archivo de todas las cuotas que nos deben
    todo= e.cartera() #Instanciamos la cartera total
    for p in [pnc for pnc in prestamos.values() if pnc[0][4]=='0' and int(pnc[0][2])<c.max_mora]: #Recorremos todos nuestros prestamos disponibles (no cedidos) y que jamas estuvieron en 4/5 de mora
        todo.agregar_prestamo(e.prestamo([c.cuota(cuota[1],cuota[2]) for cuota in cuotas[p[0][0]]],p[0][0],p[0][1],(p[0][2],p[0][3]),p[0][5])) #Agrega prestamo al instanciarlo
    #Creamos el objeto con todos los prestamos que tenemos que remplazar
    prestamos=c.csv_to_dict(c.archivos_remplazar[0])#Generamos el diccionario a partir del archivo de prestamos a remplazar
    cuotas= c.csv_to_dict(c.archivos_remplazar[1])#Generamos el diccionario a partir del archivo de cuotas de los prestamos a remplazar
    remplazar= e.cartera()#Instanciamos la cartera a remplazar
    for p in prestamos.values(): #Recorremos todos nuestros prestamos disponibles (no cedidos)
        remplazar.agregar_prestamo(e.prestamo([c.cuota(cuota[1],cuota[2]) for cuota in cuotas[p[0][0]]],p[0][0],p[0][1],(p[0][2],p[0][3]),p[0][5]))#Agrega prestamo al instanciarlo
    print("Dinero disponible: %s en %s prestamos.\nValor de la cartera a reemplazar: %s en %s prestamos."%(todo.valor_total(),len(todo.prestamos.keys()),remplazar.valor_total(), len(remplazar.prestamos.keys())))
    print("Finalizada la carga de estructuras auxiliares, llevo %s.%s segundos\n\n"%(((d.datetime.now()-T_inicio).seconds),(d.datetime.now()-T_inicio).microseconds))
    return(todo,remplazar)#Devuelve una tupla con la [0]:cartera nuestra y [1]:cartera a remplazar

def start(iteraciones,datos,algoritmo,comportamiento):
    print("Iniciamos Proceso de busqueda con algoritmo de %s en comportamiento %s."%(algoritmo.func_name, comportamiento[1]))
    T_inicio=d.datetime.now()#Iniciamos el reloj para controlar performance del metodo
    agente= problema(datos,prioridad_prestamo=(c.comparar[1],True))#Instanciamos el problema, diciendole que queremos priorizar agregar prestamos por preferencia
    lista_metas= [(algoritmo(agente, comportamiento[0])) for x in range(iteraciones)]#Lanza un for que llena la lista con el conjunto de iteraciones
    print('Tiempo utilizado para las %s iteraciones con %s ramificaciones: %s.%s segundos.'%(iteraciones,c.indice_ramificacion,(d.datetime.now()-T_inicio).seconds,(d.datetime.now()-T_inicio).microseconds))
    return sorted(lista_metas, cmp=lambda c1,c2: c.control(c2.calidad(agente.remplazar) - c1.calidad(agente.remplazar)))#Devuelve la lista de carteras metas ordenada por calidad

def estadisticas(carteras,remplazar,file_name):
    print('Estadisticos obtenidos de las %s carteras meta encontradas.'%(c.iteraciones))
    print('La mejor cartera tiene un valor total de %s, mostrando una diferencia de %s pesos con la que debia remplazar'%(carteras[0].valor_total(),abs(carteras[0].valor_total()-remplazar.valor_total())))
    print('La calidad de la misma es de %s y esta formada por %s prestamos'%(carteras[0].calidad(remplazar),len(carteras[0].prestamos)))
    print('En el conjunto de carteras, el promedio de diferencia en los prestamos es de %s pesos y la calidad promedio es de %s'%(c.prom([cartera.valor_total()-remplazar.valor_total() for cartera in carteras]),c.prom([cartera.calidad(remplazar) for cartera in carteras])))
    c.cartera_to_csv(file_name.lower(), carteras[0],remplazar)
    print('Cartera generada con colina %s.csv guardado con exito\n'%(file_name.lower()))
    

##
# Ejecucion de los algoritmos
datos= carga_archivos()#llamamos a la funcion de carga de los datos...
[estadisticas(start(c.iteraciones,datos,b.colina,comportamiento),datos[1],comportamiento[1]) for comportamiento in [(b.firstchoice,'Primera-Eleccion'), (b.bestfirst,'Primero-Mejor'), (b.estocastic,'Estocastica')]]
